﻿using mozhi.smarterp.Finance.Accounts;
using mozhi.smarterp.Finance.Basedatas.Repository;
using mozhi.smarterp.Finance.Enums;
using Mozhi.Abp.Core.Extensions;
using Mozhi.Abp.Domain.BasicData;
using Mozhi.Abp.Domain.BasicData.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;

namespace mozhi.smarterp.Finance.Finances.Services.EndPeriodProcess
{
    public class ExchangeAdjustmentEndPeriodProcessor : EndPeriodProcessorBase<AccountBalance>
    {
        private Dictionary<Guid, decimal> _exchangeRates;

        public ExchangeAdjustmentEndPeriodProcessor(EndPeriodProcessItem item, int period, Dictionary<Guid , decimal> exchangeRates , IServiceProvider serviceProvider)
            : base(item, period, serviceProvider)
        {
            _exchangeRates = exchangeRates;
        }

        protected override Task LoadCalculationDataAsync()
        {
            if (_exchangeRates == null || _exchangeRates.Count == 0)
                throw new UserFriendlyException("没有设置货币汇率", "100000");

            return base.LoadCalculationDataAsync();
        }

        protected override async Task<EndPeriodProcessResult> HandleDataAsync()
        {
            EndPeriodProcessResult result = null;

            var baseCurrencyId = (await _organizationRepository.GetAsync()).StandardCurrencyId;

            //取所有的外币余额
            _data = _data.Where(x => x.DimensionGroupValueId == null && x.CurrencyId != null && x.CurrencyId != baseCurrencyId).ToList();


            //只取资产类科目和负债类科目
            var matchAccountIds = GetMatchAccountIds();

            var matchBalances = _data.Where(x => matchAccountIds.Contains(x.AccountId)).ToList();

            if (matchAccountIds.Count == 0 || matchBalances.Count == 0)
            {
                result = new EndPeriodProcessResult(_guidGenerator.Create(), _item.Id, _period, EndPeriodProcessType.Depreciation);

                result.Change(false, false, null, null, false);

                return result;
            }

            //余额中的汇率如果比设置的币别汇率少，不能进行测算
            var currencys = _data.Select(x => x.CurrencyId.Value).Distinct().ToList();

            var unSetExchangeCurrencyIds = currencys.Where(x => !_exchangeRates.Keys.Contains(x)).ToList();

            if (unSetExchangeCurrencyIds.Count > 0)
            {
                var codes = await GetCurrencyCodesAsync(unSetExchangeCurrencyIds);

                throw new UserFriendlyException($"{string.Join(",", codes)}没有设置货币汇率", "100000");
            }

            //汇兑损失金额
            var lossAmount = 0.00M;

            //开始循环计算
            foreach (var balance in matchBalances)
            {
                var matchExchangeRate = _exchangeRates[balance.CurrencyId.Value];

                var matchAccount = _accounts.FirstOrDefault(x => x.Id == balance.AccountId);

                if (matchAccount == null)
                    continue;

                if (matchAccount.Direction == AccountDirectionType.Debit)
                {
                    //借方科目
                    var difference = GetDifferenceAmount(balance, matchExchangeRate);

                    //大于0表示收益，需要在汇兑损失科目中减去这笔钱，小于0表示损失，需要加上这笔钱
                    lossAmount = difference > 0 ? lossAmount - difference : lossAmount + Math.Abs(difference);
                }
                else
                {
                    //贷方科目
                    var difference = GetDifferenceAmount(balance, matchExchangeRate);

                    //大于0表示损失，需要在汇兑损失科目中减去这笔钱，小于0表示收益，需要加上这笔钱
                    lossAmount = difference > 0 ? lossAmount + difference : lossAmount - Math.Abs(difference);
                }
            }

            result = new EndPeriodProcessResult(_guidGenerator.Create(), _item.Id, _period, EndPeriodProcessType.FinalTransfer);

            result.Change(true, false, null, lossAmount, false);

            return result;

        }

        protected override async Task<Journal> HandleGenerateJournalAsync()
        {
            var baseCurrencyId = (await _organizationRepository.GetAsync()).StandardCurrencyId;

            _data = _data.Where(x => x.DimensionGroupValueId != null && x.CurrencyId != null && x.CurrencyId != baseCurrencyId).ToList();


            ///汇兑损失科目
            var account660302 = GetAccount(GetEndPeriodProcessSettingAsync("660302")?.CustomAccountCode);

            //只取资产类科目和负债类科目
            var matchAccountIds = GetMatchAccountIds();

            var matchBalances = _data.Where(x => matchAccountIds.Contains(x.AccountId)).ToList();

            //测算结果
            var measureResult = await GetEndPeriodResultAsync();

            var amount = measureResult.MeasureValue;

            //生成凭证分录

            var journalMananger = _serviceProvider.GetService(typeof(JournalManager)) as JournalManager;

            //按照核算维度进行结转

            var defaultJournalNumberPrefix = await GetJournalDefaultNumberPrefix();

            var journal = await journalMananger.Create(_guidGenerator.Create(), defaultJournalNumberPrefix.Id, _period.ToLastDayDateTime());

            var journalItemsCredit = GetJournalItems(matchBalances, 0, AccountDirectionType.Credit);

            var journalItemDebit = await GetJournalItemAsync("期末调汇", amount ?? 0, account660302, null, journalItemsCredit.Count, AccountDirectionType.Debit);

            var journalItems = journalItemsCredit;

            journalItems.Add(journalItemDebit);

            journal.ChangeItems(journalItems);

            return journal;
        }

        #region
        private async Task<List<string>> GetCurrencyCodesAsync(List<Guid> ids)
        {
            var currencyRepository = _serviceProvider.GetService(typeof(ICurrencyRepository)) as ICurrencyRepository;

            var currencys = new List<Currency>(); // await currencyRepository.FindAsync(new Querys.PageQuery() { Ids = ids, PageIndex = 1, PageSize = int.MaxValue });

            return currencys.Select(x=>x.Code).ToList();
        }

        /// <summary>
        /// 获取汇兑差额
        /// 某个明细科目的调汇金额 = 外币核算科目原外币余额 * 取到的当期调汇汇率 - 原本位币余额
        /// </summary>
        /// <param name="balance"></param>
        /// <param name="matchAccount"></param>
        /// <param name="exchangeRate"></param>
        /// <returns></returns>
        private decimal GetDifferenceAmount(AccountBalance balance, decimal exchangeRate)
        {
            var result = (balance.ClosingAmountFr.ToDecimal() * exchangeRate - balance.ClosingAmount.ToDecimal()).ToMoney();

            return result;
        }

        private List<Guid> GetMatchAccountIds()
        {
            //只取资产类科目和负债类科目
            var matchAccountType = new List<AccountType>() { AccountType.CurrentAsset, AccountType.NonCurrentAsset, AccountType.CurrentLiability, AccountType.NonCurrentLiability };

            var matchAccounts = _accounts.Where(x => x.IsForeignCurrencyAccounting && matchAccountType.Contains(x.AccountType) && x.IsLeaf).ToList();

            return matchAccounts.Select(x=> x.Id).ToList(); 
        }


        private List<JournalItem> GetJournalItems(List<AccountBalance> balances, int startIndex, AccountDirectionType direction)
        {
            var result = new List<JournalItem>();

            if (balances == null || balances.Count == 0)
                return result;

            foreach (var balance in balances)
            {
                var matchAccount = _accounts.FirstOrDefault(x => x.Id == balance.AccountId);

                var matchExchangeRate = _exchangeRates[balance.CurrencyId.Value];

                //找不到科目，处理不了
                if (matchAccount == null)
                    continue;

                var journalEntry = GetJournalItem(balance, matchAccount, startIndex, direction);

                if (journalEntry != null)
                {
                    result.Add(journalEntry);
                    startIndex++;
                }

            }

            return result;
        }

        /// <summary>
        /// 获取凭证分录
        /// </summary>
        /// <param name="amount"></param>
        /// <param name="account"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        private JournalItem GetJournalItem(AccountBalance balance, Account account, int index, AccountDirectionType direction)
        {
            var matchExchangeRate = _exchangeRates[balance.CurrencyId.Value];

            var amount = GetDifferenceAmount(balance, matchExchangeRate);

            //金额要处理一下，计算出来的金额要用方法反方向的，例如
            //借方科目，现在是放到了贷方，如果大于0，表示是增加了金额，如果要科目增加金额，就是用负数表示,
            //贷方科目，现在也是放到了贷方，如果大于0，表示增加了金额，如果要科目增加金额，就用本身的数字表示

            amount = account.Direction == AccountDirectionType.Debit ? -amount : amount;

            if (amount == 0)
                return null;

            var debitAmount = direction == AccountDirectionType.Debit ? (decimal?)amount : null;
            var creditAmount = direction == AccountDirectionType.Credit ? (decimal?)amount : null;

            var journalItem = new JournalItem(_guidGenerator.Create(), "期末调汇", account.Id, direction, balance.DimensionGroupValueId, 
                balance.CurrencyId.Value, matchExchangeRate, 0.00M, debitAmount, creditAmount, index);

            return journalItem;
        }

        #endregion
    }
}
